package com.janetfilter.core.attach;

import com.janetfilter.core.Launcher;
import com.janetfilter.core.utils.ProcessUtils;
import com.janetfilter.core.utils.WhereIsUtils;
import com.sun.tools.attach.VirtualMachine;

import java.io.File;
import java.io.IOException;

/**
 * 虚拟机启动器，负责将Agent附加到目标JVM
 * 支持两种方式：
 * 1. 通过VirtualMachine API直接附加
 * 2. 启动新进程执行附加操作
 */
public class VMLauncher {
    /**
     * 将Agent附加到指定的Java进程
     * 
     * @param agentFile Agent文件路径
     * @param pid 目标进程ID
     * @param args Agent参数
     */
    public static void attachVM(String agentFile, String pid, String args) {
        try {
            // 使用VirtualMachine API附加到目标进程
            // VirtualMachine是tools.jar提供的API，用于动态附加到运行中的JVM
            VirtualMachine vm = VirtualMachine.attach(pid);
            // 加载Agent，这会触发目标JVM中的agentmain方法
            vm.loadAgent(agentFile, args);
            // 分离，完成附加操作
            vm.detach();
        } catch (IOException e) {
            // 特殊处理JDK版本不匹配的情况
            if (e.getMessage().startsWith("Non-numeric value found")) {
                System.out.println("WARN: The jdk used by `ja-netfilter` does not match the attached jdk version");
            }
        } catch (Throwable e) {
            System.err.println("Attach failed: " + pid);
            e.printStackTrace(System.err);
            return;
        }

        System.out.println("ATTACHED SUCCESSFULLY: " + pid);
    }

    /**
     * 启动新进程执行附加操作
     * 
     * @param thisJar 当前Jar文件
     * @param descriptor 目标虚拟机描述符
     * @param args Agent参数
     * @throws Exception 如果启动失败
     */
    public static void launch(File thisJar, VMDescriptor descriptor, String args) throws Exception {
        // 查找java命令
        File javaCommand = WhereIsUtils.findJava();
        if (null == javaCommand) {
            throw new Exception("Can not locate java command, unable to start attach mode.");
        }

        ProcessBuilder pb;
        // 获取Java版本
        double version = Double.parseDouble(System.getProperty("java.specification.version"));
        if (version > 1.8D) {
            // Java 9+使用模块化系统，不需要显式指定tools.jar
            pb = buildProcess(javaCommand, thisJar, descriptor.getId(), args);
        } else {
            // Java 8及以下需要指定tools.jar
            File toolsJar = WhereIsUtils.findToolsJar();
            if (null == toolsJar) {
                throw new Exception("Can not locate tools.jar file, unable to start attach mode.");
            }

            pb = buildProcess(javaCommand, thisJar, descriptor.getId(), args, toolsJar);
        }

        // 启动进程并等待完成
        int exitValue = ProcessUtils.start(pb);
        if (0 != exitValue) {
            throw new Exception("Attach mode failed: " + exitValue);
        }
    }

    /**
     * 构建Java 9+的进程构建器
     * 
     * @param java Java命令文件
     * @param thisJar 当前Jar文件
     * @param id 目标进程ID
     * @param args Agent参数
     * @return 进程构建器
     */
    private static ProcessBuilder buildProcess(File java, File thisJar, String id, String args) {
        String[] cmdArray = new String[]{
                java.getAbsolutePath(),
                "-Djanf.debug=" + System.getProperty("janf.debug", "0"),  // 传递调试级别
                "-jar",
                thisJar.getAbsolutePath(),
                Launcher.ATTACH_ARG,  // 指定Attach模式
                id, args  // 目标进程ID和参数
        };

        return new ProcessBuilder(cmdArray);
    }

    /**
     * 构建Java 8及以下版本的进程构建器
     * 
     * @param java Java命令文件
     * @param thisJar 当前Jar文件
     * @param id 目标进程ID
     * @param args Agent参数
     * @param toolsJar tools.jar文件
     * @return 进程构建器
     */
    private static ProcessBuilder buildProcess(File java, File thisJar, String id, String args, File toolsJar) {
        String[] cmdArray = new String[]{
                java.getAbsolutePath(),
                "-Djanf.debug=" + System.getProperty("janf.debug", "0"),  // 传递调试级别
                "-Xbootclasspath/a:" + toolsJar.getAbsolutePath(),  // 添加tools.jar到引导类路径
                "-jar",
                thisJar.getAbsolutePath(),
                Launcher.ATTACH_ARG,  // 指定Attach模式
                id, args  // 目标进程ID和参数
        };

        return new ProcessBuilder(cmdArray);
    }
}
